#ifndef __CLASS_PROTOTHREAD_H__
#define __CLASS_PROTOTHREAD_H__

#define LC_RESUME(s)                                                           \
  do {                                                                         \
    if (s != nullptr)                                                          \
      goto *s;                                                                 \
  } while (0)

#define LC_CONCAT2(s1, s2) s1##s2
#define LC_CONCAT(s1, s2) LC_CONCAT2(s1, s2)

#define LC_SET(s)                                                              \
  do {                                                                         \
    LC_CONCAT(LC_LABEL, __LINE__) :                                            \
    (s) = &&LC_CONCAT(LC_LABEL, __LINE__);                                     \
  } while (0)
#define LC_END(s)

class Protothread {
private:
  typedef void *LineNumber;
  typedef unsigned long TimeoutNumber;
  static constexpr LineNumber LineNumberInvalid = nullptr;

protected:
  LineNumber _ptLine;
  TimeoutNumber _ptTime;

public:
  Protothread() : _ptLine(nullptr), _ptTime(0){};
  virtual ~Protothread() {}

  virtual bool Run() = 0;

  void Restart() { _ptLine = nullptr; }
  void Stop() { _ptLine = LineNumberInvalid; }
  bool IsRunning() { return _ptLine != LineNumberInvalid; }
};

#define HT_BEGIN()                                                             \
  bool ptYielded = true;                                                       \
  (void)ptYielded;                                                             \
  LC_RESUME(_ptLine)

#define HT_END()                                                               \
  LC_END(_ptLine);                                                             \
  Stop();                                                                      \
  return false;

#define HT_WAIT_UNTIL(condition)                                               \
  do {                                                                         \
    LC_SET(_ptLine);                                                           \
    if (!(condition))                                                          \
      return true;                                                             \
  } while (0)

#define HT_WAIT_WHILE(condition) HT_WAIT_UNTIL(!(condition))

#define HT_WAIT_THREAD(child) HT_WAIT_WHILE((child).Run())

#define HT_SPAWN(child)                                                        \
  do {                                                                         \
    (child).Restart();                                                         \
    HT_WAIT_THREAD(child);                                                     \
  } while (0)

#define HT_RESTART()                                                           \
  do {                                                                         \
    Restart();                                                                 \
    return true;                                                               \
  } while (0)

#define HT_EXIT()                                                              \
  do {                                                                         \
    Stop();                                                                    \
    return false;                                                              \
  } while (0)

#define HT_YIELD()                                                             \
  do {                                                                         \
    ptYielded = false;                                                         \
    LC_SET(_ptLine);                                                           \
    if (!ptYielded)                                                            \
      return true;                                                             \
  } while (0)

#define HT_YIELD_UNTIL(condition)                                              \
  do {                                                                         \
    ptYielded = false;                                                         \
    LC_SET(_ptLine);                                                           \
    if (!ptYielded || !(condition))                                            \
      return true;                                                             \
  } while (0)

#define HT_DELAY(timeout)                                                      \
  do {                                                                         \
    _ptTime = millis();                                                        \
    HT_WAIT_UNTIL(millis() - _ptTime >= timeout);                              \
  } while (0)

#define HT_MICRODELAY(timeout)                                                 \
  do {                                                                         \
    _ptTime = micros();                                                        \
    HT_WAIT_UNTIL(micros() - _ptTime >= timeout);                              \
  } while (0)

#endif //__CLASS_PROTOTHREAD_H__